// This is an instruction checker for Y86 SEQ implement
// 1. It generates clock and reset signals to make the Y86 run
// 2. Check Y86 stat signal.
// 3. It just records all GPR, CC, PC to a log file on the posedge of clock

`include "define.v"
`include "hier_pipe.v"

module Check (
    /*AUTOARG*/);

    // clock
    initial begin
        top.clock = 1'b0;
        forever begin
            #10 top.clock = ~top.clock;
        end
    end
    
    initial begin
        #200000
        $display("ERROR: system may hang!");
        $finish;
    end
    
    // reset
    initial begin
        top.reset = 1'b1;
        repeat(5)
        @(negedge top.clock);
        top.reset = 1'b0;
    end
    
    // record
    integer REC_FILE;
    initial begin
        REC_FILE = $fopen("record.log", "w");
    end

    // Pipe CC, so we can find the right CC in stage WB
    reg [2:0] W_pipeCC;
    reg [2:0] W_chkCC;
    always @(posedge top.clock, posedge top.reset)begin
        if(top.reset)begin
            W_pipeCC <= 3'b100;
            W_chkCC <= 3'b100;
        end else begin
            W_pipeCC <= `CC;
            W_chkCC <= W_pipeCC;
        end
    end

    // Pipe PC, so we can fine the right PC in stage WB
    wire [31:0] F_pipePC;
    reg [31:0] D_pipePC;
    reg [31:0] E_pipePC;
    reg [31:0] M_pipePC;
    reg [31:0] W_pipePC;
    reg [31:0] W_LastPC;
    assign F_pipePC = `PC;
    always @(posedge top.clock, posedge top.reset)begin
        if(top.reset)begin
            D_pipePC <= 32'h0000_0000;
        end else if(`DSTALL) begin
            D_pipePC <= D_pipePC;
        end else begin
            D_pipePC <= F_pipePC;
        end
    end
    always @(posedge top.clock, posedge top.reset)begin
        if(top.reset)begin
            E_pipePC <= 32'h0000_0000;
        end else if(`ESTALL) begin
            E_pipePC <= E_pipePC;
        end else begin
            E_pipePC <= D_pipePC;
        end
    end
    always @(posedge top.clock, posedge top.reset)begin
        if(top.reset)begin
            M_pipePC <= 32'h0000_0000;
        end else if(`MSTALL) begin
            M_pipePC <= M_pipePC;
        end else begin
            M_pipePC <= E_pipePC;
        end
    end
    always @(posedge top.clock, posedge top.reset)begin
        if(top.reset)begin
            W_pipePC <= 32'h0000_0000;
        end else if(`WSTALL) begin
            W_pipePC <= W_pipePC;
        end else begin
            W_pipePC <= M_pipePC;
        end
    end
    always @(posedge top.clock, posedge top.reset)begin
        if(top.reset)begin
            W_LastPC <= 32'h0000_0000;
        end else begin
            W_LastPC <= W_pipePC;
        end
    end

    integer i = 0;
    integer step = 0;
    reg [3:0] tmp;
    always @(posedge top.clock, posedge top.reset)begin
        #1; // add some hold time and then print log
        if(top.reset)begin
            step = 0;
        end else if((`W_ICODE == `IBUB) && (`W_STAT != `SAOK)) begin
            // CPU goes wrong, and add a bubble, take this as a step,
            // Since bubble won't update any registers, it is OK to check registers now
            step <= step + 1;
            if(step > 0) begin
                // Just use last PC value if anything goes wrong
                $fdisplay(REC_FILE, "%0d steps, PC = 0x%0h, Status = %h, CC = %b%b%b", 
                    step, (`STAT != `SAOK)?W_LastPC:W_pipePC, `STAT, W_chkCC[2], W_chkCC[1], W_chkCC[0]);
                $fdisplay(REC_FILE, "eax = 0x%8h", `EAX);
                $fdisplay(REC_FILE, "ecx = 0x%8h", `ECX);
                $fdisplay(REC_FILE, "edx = 0x%8h", `EDX);
                $fdisplay(REC_FILE, "ebx = 0x%8h", `EBX);
                $fdisplay(REC_FILE, "esp = 0x%8h", `ESP);
                $fdisplay(REC_FILE, "ebp = 0x%8h", `EBP);
                $fdisplay(REC_FILE, "esi = 0x%8h", `ESI);
                $fdisplay(REC_FILE, "edi = 0x%8h", `EDI);
            end
        end else if((`W_ICODE != `IBUB) || (`STAT != `SAOK)) begin
            step <= step + 1;
            if(step > 0) begin
                // Just use last PC value if anything goes wrong
                $fdisplay(REC_FILE, "%0d steps, PC = 0x%0h, Status = %h, CC = %b%b%b", 
                    step, (`STAT != `SAOK)?W_LastPC:W_pipePC, `STAT, W_chkCC[2], W_chkCC[1], W_chkCC[0]);
                $fdisplay(REC_FILE, "eax = 0x%8h", `EAX);
                $fdisplay(REC_FILE, "ecx = 0x%8h", `ECX);
                $fdisplay(REC_FILE, "edx = 0x%8h", `EDX);
                $fdisplay(REC_FILE, "ebx = 0x%8h", `EBX);
                $fdisplay(REC_FILE, "esp = 0x%8h", `ESP);
                $fdisplay(REC_FILE, "ebp = 0x%8h", `EBP);
                $fdisplay(REC_FILE, "esi = 0x%8h", `ESI);
                $fdisplay(REC_FILE, "edi = 0x%8h", `EDI);
            end
        end
        if (`STAT != `SAOK)begin
            //$fdisplay(REC_FILE, "CPU status is %h, stop simulation\n", `STAT);
            $fdisplay(REC_FILE, "Final Memory Image:");
            for(i=0; i<=`BMEMSIZE/8; i=i+1)begin
                $display("i=%d", i);
                $display("0x%4h: 0x%8h", i<<3  , {`BMEM3, `BMEM2, `BMEM1, `BMEM0});
                $display("0x%4h: 0x%8h", (i<<3)+4, {`BMEM7, `BMEM6, `BMEM5, `BMEM4});
                if({`BMEM3, `BMEM2, `BMEM1, `BMEM0} != 32'h0000_0000)begin
                    $fdisplay(REC_FILE, "0x%4h: 0x%8h", i<<3  , {`BMEM3, `BMEM2, `BMEM1, `BMEM0});
                end
                if({`BMEM7, `BMEM6, `BMEM5, `BMEM4} != 32'h0000_0000)begin
                    $fdisplay(REC_FILE, "0x%4h: 0x%8h", (i<<3)+4, {`BMEM7, `BMEM6, `BMEM5, `BMEM4});
                end
            end
            $fclose(REC_FILE);
            repeat(5)
            @(posedge top.clock);
            $finish;
        end
    end

endmodule

